﻿Imports System
Imports System.Collections.Generic
Imports System.Globalization
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Workflow.ComponentModel
Imports System.Workflow.Runtime.Tracking

Namespace System.Workflow.Runtime
    Friend Class RTTrackingProfile
        Implements ICloneable
        ' Methods
        Protected Sub New()
            Me._activities = New Dictionary([Of] String, List([Of] ActivityTrackPointCacheItem))
            Me._activitiesIgnore = New List([Of] String)
            Me._user = New Dictionary([Of] String, List([Of] UserTrackPoint))
            Me._userIgnore = New List([Of] String)
        End Sub

        Private Sub New(ByVal runtimeProfile As RTTrackingProfile)
            Me._activities = New Dictionary([Of] String, List([Of] ActivityTrackPointCacheItem))
            Me._activitiesIgnore = New List([Of] String)
            Me._user = New Dictionary([Of] String, List([Of] UserTrackPoint))
            Me._userIgnore = New List([Of] String)
            Me._profile = runtimeProfile._profile
            Me._isPrivate = runtimeProfile._isPrivate
            Me._pendingChanges = runtimeProfile._pendingChanges
            Me._pendingWorkflowChange = runtimeProfile._pendingWorkflowChange
            Me._workflowType = runtimeProfile._workflowType
            Me._activities = New Dictionary([Of] String, List([Of] ActivityTrackPointCacheItem))(runtimeProfile._activities.Count)
            Dim pair As KeyValuePair([Of] String, List([Of] ActivityTrackPointCacheItem))
            For Each pair In runtimeProfile._activities
                Me._activities.Add(pair.Key, runtimeProfile._activities.Item(pair.Key))
            Next
            Me._activitiesIgnore = New List([Of] String)(runtimeProfile._activitiesIgnore)
            If (Not runtimeProfile._dynamicActivities Is Nothing) Then
                Me._dynamicActivities = New Dictionary([Of] String, List([Of] ActivityTrackPointCacheItem))(runtimeProfile._dynamicActivities.Count)
                Dim pair2 As KeyValuePair([Of] String, List([Of] ActivityTrackPointCacheItem))
                For Each pair2 In runtimeProfile._dynamicActivities
                    Me._dynamicActivities.Add(pair2.Key, runtimeProfile._dynamicActivities.Item(pair2.Key))
                Next
            End If
            If (Not runtimeProfile._dynamicActivitiesIgnore Is Nothing) Then
                Me._dynamicActivitiesIgnore = New List([Of] String)(runtimeProfile._dynamicActivitiesIgnore)
            End If
            Me._user = New Dictionary([Of] String, List([Of] UserTrackPoint))(runtimeProfile._user.Count)
            Dim pair3 As KeyValuePair([Of] String, List([Of] UserTrackPoint))
            For Each pair3 In runtimeProfile._user
                Me._user.Add(pair3.Key, runtimeProfile._user.Item(pair3.Key))
            Next
            Me._userIgnore = New List([Of] String)(runtimeProfile._userIgnore)
            If (Not runtimeProfile._dynamicUser Is Nothing) Then
                Me._dynamicUser = New Dictionary([Of] String, List([Of] UserTrackPoint))(runtimeProfile._dynamicUser.Count)
                Dim pair4 As KeyValuePair([Of] String, List([Of] UserTrackPoint))
                For Each pair4 In runtimeProfile._dynamicUser
                    Me._dynamicUser.Add(pair4.Key, pair4.Value)
                Next
            End If
            If (Not runtimeProfile._dynamicUserIgnore Is Nothing) Then
                Me._dynamicUserIgnore = New List([Of] String)(runtimeProfile._dynamicUserIgnore)
            End If
        End Sub

        Friend Sub New(ByVal profile As TrackingProfile, ByVal root As Activity, ByVal serviceType As Type)
            Me._activities = New Dictionary([Of] String, List([Of] ActivityTrackPointCacheItem))
            Me._activitiesIgnore = New List([Of] String)
            Me._user = New Dictionary([Of] String, List([Of] UserTrackPoint))
            Me._userIgnore = New List([Of] String)
            If (profile Is Nothing) Then
                Throw New ArgumentNullException("profile")
            End If
            If (root Is Nothing) Then
                Throw New ArgumentNullException("root")
            End If
            If (serviceType Is Nothing) Then
                Throw New ArgumentNullException("serviceType")
            End If
            Me._workflowType = root.GetType
            Me._serviceType = serviceType
            Dim serializer As New TrackingProfileSerializer
            Dim writer As New StringWriter(CultureInfo.InvariantCulture)
            Dim reader As StringReader = Nothing
            Dim profile2 As TrackingProfile = Nothing
            Try 
                serializer.Serialize(writer, profile)
                reader = New StringReader(writer.ToString)
                profile2 = serializer.Deserialize(reader)
            Finally
                If (Not reader Is Nothing) Then
                    reader.Close
                End If
                If (Not writer Is Nothing) Then
                    writer.Close
                End If
            End Try
            Me._profile = profile2
            Me.CheckAllActivities(root)
        End Sub

        Friend Function ActivitySubscriptionNeeded(ByVal activity As Activity) As Boolean
            Dim list As List([Of] ActivityTrackPointCacheItem) = Nothing
            If (Not Me._pendingWorkflowChange OrElse (Me._pendingWorkflowChange AndAlso Not Me.IsPendingUpdateActivity(activity, True))) Then
                Dim flag As Boolean = True
                Do While flag
                    If Me._activitiesIgnore.Contains(activity.QualifiedName) Then
                        Return False
                    End If
                    If Me._activities.TryGetValue(activity.QualifiedName, list) Then
                        Return True
                    End If
                    Me.CheckActivity(activity)
                Loop
                Return False
            End If
            Dim includes As List([Of] UserTrackPoint) = Nothing
            If Me.CreateCacheItems(activity, includes) Then
                Me.CacheInsertUpdatePending(activity.QualifiedName, includes)
            Else
                Me._dynamicUserIgnore.Add(activity.QualifiedName)
            End If
            If Me.CreateCacheItems(activity, list) Then
                Me.CacheInsertUpdatePending(activity.QualifiedName, list)
                Return True
            End If
            Me._dynamicActivitiesIgnore.Add(activity.QualifiedName)
            Return False
        End Function

        Private Sub CacheInsert(ByVal qualifiedID As String, ByVal points As List([Of] ActivityTrackPointCacheItem))
            If Me._activities.ContainsKey(qualifiedID) Then
                Throw New InvalidOperationException(ExecutionStringManager.RTProfileActCacheDupKey)
            End If
            Dim item As ActivityTrackPointCacheItem
            For Each item In points
                Me.CacheInsert(qualifiedID, item)
            Next
        End Sub

        Private Sub CacheInsert(ByVal qualifiedID As String, ByVal points As List([Of] UserTrackPoint))
            If Me._user.ContainsKey(qualifiedID) Then
                Throw New InvalidOperationException(ExecutionStringManager.RTProfileActCacheDupKey)
            End If
            Dim point As UserTrackPoint
            For Each point In points
                Me.CacheInsert(qualifiedID, point)
            Next
        End Sub

        Private Sub CacheInsert(ByVal qualifiedID As String, ByVal point As ActivityTrackPointCacheItem)
            Dim list As List([Of] ActivityTrackPointCacheItem) = Nothing
            If Not Me._activities.TryGetValue(qualifiedID, list) Then
                list = New List([Of] ActivityTrackPointCacheItem)
                Me._activities.Add(qualifiedID, list)
            End If
            list.Add(point)
        End Sub

        Private Sub CacheInsert(ByVal qualifiedID As String, ByVal point As UserTrackPoint)
            Dim list As List([Of] UserTrackPoint) = Nothing
            If Not Me._user.TryGetValue(qualifiedID, list) Then
                list = New List([Of] UserTrackPoint)
                Me._user.Add(qualifiedID, list)
            End If
            list.Add(point)
        End Sub

        Private Sub CacheInsertUpdatePending(ByVal qualifiedID As String, ByVal points As List([Of] ActivityTrackPointCacheItem))
            If (Not Me._isPrivate OrElse Not Me._pendingWorkflowChange) Then
                Throw New InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate)
            End If
            If (Me._dynamicActivities Is Nothing) Then
                Throw New InvalidOperationException(ExecutionStringManager.RTProfileDynamicActCacheIsNull)
            End If
            Dim list As List([Of] ActivityTrackPointCacheItem) = Nothing
            If Not Me._dynamicActivities.TryGetValue(qualifiedID, list) Then
                list = New List([Of] ActivityTrackPointCacheItem)
                Me._dynamicActivities.Add(qualifiedID, list)
            End If
            Dim item As ActivityTrackPointCacheItem
            For Each item In points
                list.Add(item)
            Next
        End Sub

        Private Sub CacheInsertUpdatePending(ByVal qualifiedID As String, ByVal points As List([Of] UserTrackPoint))
            If (Not Me._isPrivate OrElse Not Me._pendingWorkflowChange) Then
                Throw New InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate)
            End If
            If (Me._dynamicUser Is Nothing) Then
                Throw New InvalidOperationException(ExecutionStringManager.RTProfileDynamicActCacheIsNull)
            End If
            Dim list As List([Of] UserTrackPoint) = Nothing
            If Not Me._dynamicUser.TryGetValue(qualifiedID, list) Then
                list = New List([Of] UserTrackPoint)
                Me._dynamicUser.Add(qualifiedID, list)
            End If
            Dim point As UserTrackPoint
            For Each point In points
                list.Add(point)
            Next
        End Sub

        Private Sub CheckActivity(ByVal activity As Activity)
            Dim qualifiedName As String = activity.QualifiedName
            Dim includes As List([Of] ActivityTrackPointCacheItem) = Nothing
            If Me.CreateCacheItems(activity, includes) Then
                Me.CacheInsert(qualifiedName, includes)
            Else
                Me._activitiesIgnore.Add(qualifiedName)
            End If
            Dim list2 As List([Of] UserTrackPoint) = Nothing
            If Me.CreateCacheItems(activity, list2) Then
                Me.CacheInsert(qualifiedName, list2)
            Else
                Me._userIgnore.Add(qualifiedName)
            End If
        End Sub

        Private Sub CheckAllActivities(ByVal activity As Activity)
            Me.CheckActivity(activity)
            If TypeOf activity Is CompositeActivity Then
                Dim activity2 As Activity
                For Each activity2 In Me.GetAllEnabledActivities(DirectCast(activity, CompositeActivity))
                    Me.CheckAllActivities(activity2)
                Next
            End If
        End Sub

        Friend Function Clone() As RTTrackingProfile
            Return New RTTrackingProfile(Me)
        End Function

        Private Function CreateCacheItems(ByVal activity As Activity, <Out> ByRef includes As List([Of] ActivityTrackPointCacheItem)) As Boolean
            includes = New List([Of] ActivityTrackPointCacheItem)
            Dim point As ActivityTrackPoint
            For Each point In Me._profile.ActivityTrackPoints
                Dim list As List([Of] ActivityExecutionStatus)
                Dim hasCondition As Boolean = False
                If point.IsMatch(activity, list, hasCondition) Then
                    includes.Add(New ActivityTrackPointCacheItem(point, list, hasCondition))
                End If
            Next
            Return (includes.Count > 0)
        End Function

        Private Function CreateCacheItems(ByVal activity As Activity, <Out> ByRef includes As List([Of] UserTrackPoint)) As Boolean
            includes = New List([Of] UserTrackPoint)
            Dim point As UserTrackPoint
            For Each point In Me._profile.UserTrackPoints
                If point.IsMatch(activity) Then
                    includes.Add(point)
                End If
            Next
            Return (includes.Count > 0)
        End Function

        Public Function GetAllEnabledActivities(ByVal compositeActivity As CompositeActivity) As IList([Of] Activity)
            If (compositeActivity Is Nothing) Then
                Throw New ArgumentNullException("compositeActivity")
            End If
            Dim list As New List([Of] Activity)(compositeActivity.EnabledActivities)
            Dim activity As Activity
            For Each activity In DirectCast(compositeActivity, ISupportAlternateFlow).AlternateFlowActivities
                If Not list.Contains(activity) Then
                    list.Add(activity)
                End If
            Next
            Return list
        End Function

        Private Function IsPendingUpdateActivity(ByVal activity As Activity, ByVal addedOnly As Boolean) As Boolean
            If (Not Me._isPrivate OrElse Not Me._pendingWorkflowChange) Then
                Throw New InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate)
            End If
            If ((Not Me._pendingChanges Is Nothing) AndAlso (Me._pendingChanges.Count > 0)) Then
                Dim action As WorkflowChangeAction
                For Each action In Me._pendingChanges
                    Dim strB As String = Nothing
                    If TypeOf action Is ActivityChangeAction Then
                        If TypeOf action Is AddedActivityAction Then
                            strB = DirectCast(action, AddedActivityAction).AddedActivity.QualifiedName
                        ElseIf (TypeOf action Is RemovedActivityAction AndAlso Not addedOnly) Then
                            strB = DirectCast(action, RemovedActivityAction).OriginalRemovedActivity.QualifiedName
                        End If
                        If ((Not strB Is Nothing) AndAlso (String.Compare(activity.QualifiedName, strB, StringComparison.Ordinal) = 0)) Then
                            Return True
                        End If
                    End If
                Next
            End If
            Return False
        End Function

        Private Function System.ICloneable.Clone() As Object Implements ICloneable.Clone
            Return Me.Clone
        End Function

        Private Function TryGetCacheItems(ByVal activity As Activity, <Out> ByRef points As List([Of] ActivityTrackPointCacheItem)) As Boolean
            points = Nothing
            If (Me._pendingWorkflowChange AndAlso (Not Me._pendingWorkflowChange OrElse Me.IsPendingUpdateActivity(activity, True))) Then
                Return Me._dynamicActivities.TryGetValue(activity.QualifiedName, points)
            End If
            Return Me._activities.TryGetValue(activity.QualifiedName, points)
        End Function

        Private Function TryGetCacheItems(ByVal activity As Activity, <Out> ByRef points As List([Of] UserTrackPoint)) As Boolean
            points = Nothing
            If (Me._pendingWorkflowChange AndAlso (Not Me._pendingWorkflowChange OrElse Me.IsPendingUpdateActivity(activity, True))) Then
                Return Me._dynamicUser.TryGetValue(activity.QualifiedName, points)
            End If
            Return Me._user.TryGetValue(activity.QualifiedName, points)
        End Function

        Friend Function TryTrackActivityEvent(ByVal activity As Activity, ByVal status As ActivityExecutionStatus, ByVal provider As IServiceProvider, ByVal record As ActivityTrackingRecord) As Boolean
            Dim list As List([Of] ActivityTrackPointCacheItem)
            If Not Me.TryGetCacheItems(activity, list) Then
                Return False
            End If
            Dim flag As Boolean = False
            Dim item As ActivityTrackPointCacheItem
            For Each item In list
                If ((Not item.HasLocationConditions OrElse item.Point.IsMatch(activity, status)) AndAlso item.Events.Contains(status)) Then
                    flag = True
                    item.Point.Track(activity, provider, record.Body)
                    record.Annotations.AddRange(item.Point.Annotations)
                End If
            Next
            Return flag
        End Function

        Friend Function TryTrackInstanceEvent(ByVal status As TrackingWorkflowEvent, ByVal record As WorkflowTrackingRecord) As Boolean
            Dim flag As Boolean = False
            Dim point As WorkflowTrackPoint
            For Each point In Me._profile.WorkflowTrackPoints
                If point.IsMatch(status) Then
                    record.Annotations.AddRange(point.Annotations)
                    flag = True
                End If
            Next
            Return flag
        End Function

        Friend Function TryTrackUserEvent(ByVal activity As Activity, ByVal keyName As String, ByVal argument As Object, ByVal exec As WorkflowExecutor, ByVal record As UserTrackingRecord) As Boolean
            Dim list As List([Of] UserTrackPoint)
            If Not Me.TryGetCacheItems(activity, list) Then
                Return False
            End If
            Dim flag As Boolean = False
            Dim point As UserTrackPoint
            For Each point In list
                If point.IsMatch(activity, keyName, argument) Then
                    flag = True
                    point.Track(activity, argument, exec, record.Body)
                    record.Annotations.AddRange(point.Annotations)
                End If
            Next
            Return flag
        End Function

        Public Sub WorkflowChangeBegin(ByVal changeActions As IList([Of] WorkflowChangeAction))
            If Me._pendingWorkflowChange Then
                Throw New InvalidOperationException(ExecutionStringManager.DynamicUpdateIsNotPending)
            End If
            If Not Me._isPrivate Then
                Throw New InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate)
            End If
            Me._dynamicActivities = New Dictionary([Of] String, List([Of] ActivityTrackPointCacheItem))
            Me._dynamicActivitiesIgnore = New List([Of] String)
            Me._dynamicUser = New Dictionary([Of] String, List([Of] UserTrackPoint))
            Me._dynamicUserIgnore = New List([Of] String)
            Me._pendingChanges = changeActions
            Me._pendingWorkflowChange = True
        End Sub

        Public Sub WorkflowChangeCommit()
            If Me._pendingWorkflowChange Then
                If Not Me._isPrivate Then
                    Throw New InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate)
                End If
                If (Not Me._pendingChanges Is Nothing) Then
                    Dim action As WorkflowChangeAction
                    For Each action In Me._pendingChanges
                        If TypeOf action Is RemovedActivityAction Then
                            Dim qualifiedName As String = DirectCast(action, RemovedActivityAction).OriginalRemovedActivity.QualifiedName
                            Me._activities.Remove(qualifiedName)
                            Me._activitiesIgnore.Remove(qualifiedName)
                            Me._user.Remove(qualifiedName)
                            Me._userIgnore.Remove(qualifiedName)
                        End If
                    Next
                End If
                If ((Not Me._dynamicActivities Is Nothing) AndAlso (Me._dynamicActivities.Count > 0)) Then
                    Dim pair As KeyValuePair([Of] String, List([Of] ActivityTrackPointCacheItem))
                    For Each pair In Me._dynamicActivities
                        Me._activities.Add(pair.Key, pair.Value)
                    Next
                End If
                If ((Not Me._dynamicActivitiesIgnore Is Nothing) AndAlso (Me._dynamicActivitiesIgnore.Count > 0)) Then
                    Me._activitiesIgnore.AddRange(Me._dynamicActivitiesIgnore)
                End If
                If ((Not Me._dynamicUser Is Nothing) AndAlso (Me._dynamicUser.Count > 0)) Then
                    Dim pair2 As KeyValuePair([Of] String, List([Of] UserTrackPoint))
                    For Each pair2 In Me._dynamicUser
                        Me._user.Add(pair2.Key, pair2.Value)
                    Next
                End If
                If ((Not Me._dynamicUserIgnore Is Nothing) AndAlso (Me._dynamicUserIgnore.Count > 0)) Then
                    Me._userIgnore.AddRange(Me._dynamicUserIgnore)
                End If
                Me._dynamicActivities = Nothing
                Me._dynamicActivitiesIgnore = Nothing
                Me._dynamicUser = Nothing
                Me._dynamicUserIgnore = Nothing
                Me._pendingChanges = Nothing
                Me._pendingWorkflowChange = False
            End If
        End Sub

        Public Sub WorkflowChangeRollback()
            Me._dynamicActivities = Nothing
            Me._dynamicActivitiesIgnore = Nothing
            Me._dynamicUser = Nothing
            Me._dynamicUserIgnore = Nothing
            Me._pendingChanges = Nothing
            Me._pendingWorkflowChange = False
        End Sub


        ' Properties
        Friend Property IsPrivate As Boolean
            Get
                Return Me._isPrivate
            End Get
            Set(ByVal value As Boolean)
                If (Not value AndAlso Me._isPrivate) Then
                    Throw New InvalidOperationException(ExecutionStringManager.CannotResetIsPrivate)
                End If
                Me._isPrivate = value
            End Set
        End Property

        Friend ReadOnly Property Version As Version
            Get
                Return Me._profile.Version
            End Get
        End Property

        Friend ReadOnly Property WorkflowType As Type
            Get
                Return Me._workflowType
            End Get
        End Property


        ' Fields
        Private _activities As Dictionary([Of] String, List([Of] ActivityTrackPointCacheItem))
        Private _activitiesIgnore As List([Of] String)
        Private _dynamicActivities As Dictionary([Of] String, List([Of] ActivityTrackPointCacheItem))
        Private _dynamicActivitiesIgnore As List([Of] String)
        Private _dynamicUser As Dictionary([Of] String, List([Of] UserTrackPoint))
        Private _dynamicUserIgnore As List([Of] String)
        Private _isPrivate As Boolean
        Private _pendingChanges As IList([Of] WorkflowChangeAction)
        Private _pendingWorkflowChange As Boolean
        Private _profile As TrackingProfile
        Private _serviceType As Type
        Private _user As Dictionary([Of] String, List([Of] UserTrackPoint))
        Private _userIgnore As List([Of] String)
        Private _workflowType As Type

        ' Nested Types
        <StructLayout(LayoutKind.Sequential)> _
        Private Structure ActivityTrackPointCacheItem
            Friend Point As ActivityTrackPoint
            Friend Events As List([Of] ActivityExecutionStatus)
            Friend HasLocationConditions As Boolean
            Friend Sub New(ByVal point As ActivityTrackPoint, ByVal events As List([Of] ActivityExecutionStatus), ByVal hasConditions As Boolean)
                If (point Is Nothing) Then
                    Throw New ArgumentNullException("point")
                End If
                If (events Is Nothing) Then
                    Throw New ArgumentNullException("events")
                End If
                Me.Point = point
                Me.Events = events
                Me.HasLocationConditions = hasConditions
            End Sub
        End Structure
    End Class
End Namespace

